//
//   Copyright 2011 Gynvael Coldwind & Mateusz "j00ru" Jurczyk
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
#include <stdint.h>
#include "record.h"
#include "global.h"
#include "gynvael.h"
#include "j00ru.h"
#include "music.h"
using namespace std;

void rec_ApplyEvent(EVENT_STRUCT* event)
{
  switch(event->type)
  {
    case EVENT_PLAYER_SET_XY:
      g_GameState.players[event->u.playerSetXY.id].x    = event->u.playerSetXY.x;
      g_GameState.players[event->u.playerSetXY.id].y    = event->u.playerSetXY.y;
      g_GameState.players[event->u.playerSetXY.id].map  = event->u.playerSetXY.map;
      g_GameState.players[event->u.playerSetXY.id].pos  = event->u.playerSetXY.pos;
      break;

    case EVENT_CREATE_OBJECT:
      if(event->u.createObject.map_id == g_GameState.currentLevel->activeMap)
        music_playSound(SND_SHOOT);

      object_Insert(&event->u.createObject);
      break;

    case EVENT_MODIFY_TILE:
      memcpy(&g_GameState.currentLevel->maps[event->u.modifyTile.map]->tiles[event->u.modifyTile.y][event->u.modifyTile.x],
             &event->u.modifyTile.tile,
             sizeof(TILE_STRUCT));
      break;

    case EVENT_MODIFY_PARAMS:
      g_ObjectParams[event->u.modifyParams.index][event->u.modifyParams.paramNo] = event->u.modifyParams.value;
      break;

    default:
      // unknown event
      break;
  }
}

void rec_Reset(RECORDER_STRUCT* recorder)
{
  recorder->startTick = g_Ticks;

  for( unsigned i = 0; i < recorder->tracks.size(); i++ )
  {
    recorder->tracks[i]->currentlyPlayedEvent = 0;
  }
}

void rec_Clean(RECORDER_STRUCT* recorder)
{
  //
  // Clean the entire recorder structure
  //
  for( unsigned i = 0; i < recorder->initialState.size(); i++ )
    delete recorder->initialState[i];
  recorder->initialState.clear();

  for( unsigned i = 0; i < recorder->tracks.size(); i++ )
  {
    for( unsigned j = 0; j < recorder->tracks[i]->events.size(); j++ )
      delete recorder->tracks[i]->events[j];
    recorder->tracks.clear();
  }

  for( unsigned i = 0; i < recorder->currTrack.size(); i++ )
    delete recorder->currTrack[i];
  recorder->currTrack.clear();
}


void rec_SaveCurrentTrack(RECORDER_STRUCT* recorder)
{
  TRACK_STRUCT* track = new TRACK_STRUCT;
  if(track == NULL) // highly unlikely
    return;

  track->currentlyPlayedEvent = 0;
  track->events = recorder->currTrack;

  recorder->tracks.push_back(track);

  recorder->currTrack.clear();
}

void rec_Insert(RECORDER_STRUCT* recorder, EVENT_STRUCT* event)
{
  EVENT_STRUCT* newEvent = new EVENT_STRUCT;
  if(newEvent == NULL) // highly unlikely
    return;

  memcpy(newEvent, event, sizeof(EVENT_STRUCT));
  newEvent->tick = g_Ticks - recorder->startTick;

  recorder->currTrack.push_back(newEvent);
}

void rec_InsertCurrentPlayer(RECORDER_STRUCT* recorder)
{
  EVENT_STRUCT event;

  event.type = EVENT_PLAYER_SET_XY;
  event.tick = g_Ticks; // unimportant

  event.u.playerSetXY.id   = g_GameState.activePlayer;
  event.u.playerSetXY.map  = g_GameState.players[g_GameState.activePlayer].map;
  event.u.playerSetXY.x    = g_GameState.players[g_GameState.activePlayer].x;
  event.u.playerSetXY.y    = g_GameState.players[g_GameState.activePlayer].y;
  event.u.playerSetXY.pos  = g_GameState.players[g_GameState.activePlayer].pos;

  rec_Insert(recorder, &event);
}

void rec_InsertModifyTile(RECORDER_STRUCT* recorder, int x, int y, int map, TILE_STRUCT* tile)
{
  EVENT_STRUCT event;

  event.type = EVENT_MODIFY_TILE;
  event.tick = g_Ticks;

  event.u.modifyTile.x    = x;
  event.u.modifyTile.y    = y;
  event.u.modifyTile.map  = map;
  memcpy(&event.u.modifyTile.tile, tile, sizeof(TILE_STRUCT));

  rec_Insert(recorder, &event);
}

void rec_InsertModifyParams(RECORDER_STRUCT* recorder, int index, int paramNo, int value)
{
  EVENT_STRUCT event;
  
  event.type = EVENT_MODIFY_PARAMS;
  event.tick = g_Ticks;

  event.u.modifyParams.index    = index;
  event.u.modifyParams.paramNo  = paramNo;
  event.u.modifyParams.value    = value;

  rec_Insert(recorder, &event);
}

void rec_MakeSnapshot(RECORDER_STRUCT* recorder)
{
  // Record the global parameters' array state
  memcpy(recorder->objectParams, g_ObjectParams, sizeof(g_ObjectParams));
  
  // Record the state of all players
  for( int i = 0; i < g_GameState.currentLevel->playerCount; i++ )
  {
    EVENT_STRUCT* event = new EVENT_STRUCT; 

    event->type = EVENT_PLAYER_SET_XY;
    event->tick = 0; // not important, really

    event->u.playerSetXY.id = i;
    event->u.playerSetXY.map = g_GameState.players[i].map;
    event->u.playerSetXY.x  = g_GameState.players[i].x;
    event->u.playerSetXY.y  = g_GameState.players[i].y;

    recorder->initialState.push_back(event);
  }

  // TODO: other objects
}

void rec_RestoreInitState(RECORDER_STRUCT* recorder)
{
  // TODO: reset the g_Objects global object table

  // Clean all objects
  for( int i = 0; i < g_Objects.size(); i++ )
    delete g_Objects[i];
  g_Objects.clear();

  // Copy the global params array state
  memcpy(g_ObjectParams, recorder->objectParams, sizeof(g_ObjectParams));

  // Apply all initial events
  for( unsigned i = 0; i < recorder->initialState.size(); i++ )
    rec_ApplyEvent(recorder->initialState[i]);

  delete g_GameState.currentLevel;

  g_GameState.currentLevel = game_LoadLevel();
  if(g_GameState.currentLevel == NULL)
  {
    // TODO: add something more appropriate here
    puts("error: unable to read a level file which was otherwise successfull before.");
    return;
  }

  g_GameState.currentLevel->activeMap = 0;
  for( int i = 0; i < g_GameState.currentLevel->playerCount; i++ )
  {
    g_GameState.players[i].map = 0;
    g_GameState.players[i].pos = 0;
  }
}

void rec_PlayAvailableObjects(RECORDER_STRUCT* recorder)
{
  // Go through all recorded tracks
  for( unsigned i = 0; i < recorder->tracks.size(); i++ )
  {
    while(recorder->tracks[i]->currentlyPlayedEvent < (int)recorder->tracks[i]->events.size() &&
          (int)g_Ticks - recorder->startTick >= recorder->tracks[i]->events[recorder->tracks[i]->currentlyPlayedEvent]->tick)
    {
      rec_ApplyEvent(recorder->tracks[i]->events[recorder->tracks[i]->currentlyPlayedEvent]);
      recorder->tracks[i]->currentlyPlayedEvent++;
    }
  }
}

